<?php
/*
    ainitramfs program file
    Copyright (C) Ambroz Bizjak, 2010

    This file is part of ainitramfs.

    ainitramfs is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License version 2
    as published by the Free Software Foundation.

    ainitramfs is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License along
    with this program; if not, write to the Free Software Foundation, Inc.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

$ai_name = "ainitramfs";
$ai_version = "1.32";
$ai_copyright = "Copyright (C) Ambroz Bizjak, 2010";

$ai_dataroot = dirname($_SERVER['SCRIPT_FILENAME']);

require "{$ai_dataroot}/functions.php";

function assert_failure ($script, $line, $message)
{
    if ($message == "") {
        ai_error("Assertion failure at {$script}:{$line}");
    } else {
        ai_error("Assertion failure at {$script}:{$line}: {$message}");
    }
}

assert_options(ASSERT_CALLBACK, "assert_failure");

// handle arguments

function show_usage ()
{
    global $ai_name;
    global $ai_version;
    global $ai_copyright;

    echo <<<EOD
{$ai_name} version {$ai_version}
{$ai_copyright}
Generates an initial RAM filesystem.
Options:
    --root <dir>            Use the system in this directory. Default: "/".
    --kmodroot <dir>        Use kernel modules from this directory. Default: <root>.
    --kernel <version>      Generate an initramfs for this kernel version.
                            Default: value of `uname -r`.
    --temp <dir>            Use this directory for temporary storage. The
                            directory must not exist, its parent must exist.
    --toolchain <prefix>    Use this prefix for toolchain programs.
                            Default: none.
    --imageout <file>       Write the initramfs to this file.
                            Default: "<root>/boot/initrd-<kernel>".
    --noclean               Do not remove the temporary storage directory before
                            exiting, but print its path instead.
    --script <file>         Add a script to the script list. These are run as
                            part of initramfs generation.
    --noscripts             Remove all scripts on the script list.
    --option <mod> <name> <val>
                            Pass an option to the scripts.
                            The default "base.php" script understands these options:
                                common ata <yes/no> (default: yes)
                                common usb <yes/no> (default: yes)
                                base network <yes/no> (default: no)
                                base nfs <yes/no> (default: no)
    --help                  Show help (this text) and exit, without generating
                            the initramfs.
    --version               Show version information and exit, without
                            generating the initramfs.

EOD;
}

function show_version ()
{
    global $ai_name;
    global $ai_version;
    global $ai_copyright;

    echo <<<EOD
{$ai_name} version {$ai_version}
{$ai_copyright}

EOD;
}

$ai_root = "";
$ai_kmodroot = "";
$ai_kernel = "";
$ai_tempopt = "";
$ai_temp = "";
$ai_toolchain = "";
$ai_imageout = "";
$ai_noclean = 0;
$ai_scripts = array(array("type" => "name", "name" => "base"));
$ai_options = array();

for ($i = 1; $i < $argc;) {
    $arg = $argv[$i++];
    switch ($arg) {
        case "--root":
            if ($argc - $i < 1) {
                ai_error("${arg} requires 1 argument");
            }
            $ai_root = $argv[$i++];
            break;
        case "--kmodroot":
            if ($argc - $i < 1) {
                ai_error("${arg} requires 1 argument");
            }
            $ai_kmodroot = $argv[$i++];
            break;
        case "--kernel":
            if ($argc - $i < 1) {
                ai_error("${arg} requires 1 argument");
            }
            $ai_kernel = $argv[$i++];
            break;
        case "--temp":
            if ($argc - $i < 1) {
                ai_error("${arg} requires 1 argument");
            }
            $ai_tempopt = $argv[$i++];
            break;
        case "--toolchain":
            if ($argc - $i < 1) {
                ai_error("${arg} requires 1 argument");
            }
            $ai_toolchain = $argv[$i++];
            break;
        case "--imageout":
            if ($argc - $i < 1) {
                ai_error("${arg} requires 1 argument");
            }
            $ai_imageout = $argv[$i++];
            break;
        case "--noclean":
            $ai_noclean = 1;
            break;
        case "--script":
            if ($argc - $i < 1) {
                ai_error("${arg} requires 1 argument");
            }
            array_push($ai_scripts, array("type" => "file", "file" => $argv[$i++]));
            break;
        case "--noscripts":
            $ai_scripts = array();
            break;
        case "--option":
            if ($argc - $i < 3) {
                ai_error("${arg} requires 3 arguments");
            }
            $opt_dest = $argv[$i++];
            $opt_name = $argv[$i++];
            $opt_val = $argv[$i++];
            $ai_options[$opt_dest][$opt_name] = $opt_val;
            break;
        case "--help":
            show_usage();
            exit(0);
        case "--version":
            show_version();
            exit(0);
        default:
            ai_error("Unknown option: $arg");
    }
}

if ($ai_root == "") {
    $ai_root = "/";
}

if ($ai_kmodroot == "") {
    $ai_kmodroot = $ai_root;  
}

if ($ai_kernel == "") {
    $ai_kernel = exec("uname -r");
}

if ($ai_tempopt == "") {
    $dir = exec("mktemp -d --tmpdir ainitramfs-XXXXXXXXXX");
    if (!is_dir($dir)) {
        ai_error("cannot create temp folder");
    }
    $ai_temp = ai_path($dir);
} else {
    if (!mkdir($ai_tempopt)) {
        ai_error("cannot create temp folder: {$ai_out}");
    }
    $ai_temp = ai_path($ai_tempopt);
}

if ($ai_imageout == "") {
    $ai_imageout = ai_join_paths($ai_root, "boot", "initrd-{$ai_kernel}");
}

$ai_out = ai_join_paths($ai_temp, "out");
$ai_kmoddir = "/lib/modules/{$ai_kernel}";
$ai_finfo = finfo_open();

// create out folder
if (!mkdir($ai_out)) {
    ai_error("cannot create output folder: {$ai_out}");
}

// read library paths
$ai_ldpaths = array("/lib", "/usr/lib");
$ai_ldpaths_concat = "/lib\n/usr/lib\n";
ai_parse_ldconfig(ai_join_paths($ai_root, "/etc/ld.so.conf"), $ai_root, $ai_ldpaths, $ai_ldpaths_concat);

// install etc
ai_install_fs_dir("/etc");

// write ld.so.conf
if (file_put_contents(ai_join_paths($ai_out, "/etc/ld.so.conf"), $ai_ldpaths_concat) === FALSE) {
    ai_error("failed to write ld.so.conf");
}

// traverse the kernel modules directory and associate module names with paths
$ai_kmods = array();
ai_read_kmods("");

// first install /lib, obeying symlinks in the root. If we don't do this, we could
// install the directory /lib, but in $ai_root /lib is a symlink to lib64.
ai_install_fs_dir("/lib", $ai_root);

// install module directory
ai_install_fs_dir($ai_kmoddir, $ai_kmodroot);

// run scripts
foreach ($ai_scripts as $script) {
    switch ($script["type"]) {
        case "name":
            $scriptfile = "{$ai_dataroot}/scripts/{$script["name"]}.php";
            break;
        case "file":
            $scriptfile = $script["file"];
            break;
        default:
            ai_error("Unknown script type {$script["type"]}");
    }
    if (!include($scriptfile)) {
        ai_error("cannot include script $scriptfile");
    }
}

// make sure all option destinations existed
if (count($ai_options) > 0) {
    ai_error("option destinations did not exist: " . implode(", ", array_keys($ai_options)));
}

// generate linker cache
if (ai_invoke_shell("ldconfig -r ".escapeshellarg($ai_out)) != 0) {
    ai_error("ldconfig failed");
}

/* Don't do depmod; it doesn't work for a different architecture than the host. Do it at runtime instead.
// generate kernel module dependencies
if (ai_invoke_shell("depmod -b ".escapeshellarg($ai_out)." ".escapeshellarg($ai_kernel)) != 0) {
    ai_error("depmod failed");
}
*/

// generate image
$imagepath = ai_join_paths($ai_temp, "initramfs.img");
if (ai_invoke_shell("(cd ".escapeshellarg($ai_out)." && find . | cpio -H newc -o | gzip) > ".escapeshellarg($imagepath)) != 0) {
    ai_error("build image failed");
}

// copy image
if (!copy($imagepath, $ai_imageout)) {
    ai_error("copy image failed");
}

// remove temp folder
ai_cleanup();
